import { Vector3, Vector3Tuple } from 'three-legacy';
import {
  ColorSchema,
  CSArrow,
  CSLine,
  ParameterObjectType,
  PickType,
} from '@amcax/base';
import { getRenderTypeByDxid } from '../render';
import { ctx2 } from '@app-base/module';
import { MeasureLabel } from './Label';
import { getFaceCenterByPickId } from '.';
import { MeasureChildObject } from './ChildObject';
import { MeasureAngleResultWithLabel } from './types';

export class AngleLabel extends MeasureChildObject {
  getRatioDistance(start: Vector3, end: Vector3) {
    const length = start.clone().sub(end).length();

    const camera = ctx2.viewer.sceneSetup.oCamera;

    return (length / (camera.top - camera.bottom)) * camera.zoom;
  }

  vertexs: Vector3[];

  static readonly color = 0x002596;

  constructor(
    line1: [Vector3, Vector3],
    line2: [Vector3, Vector3],
    measureResult: MeasureAngleResultWithLabel,
  ) {
    super();

    const getLineVector = (line: [Vector3, Vector3]) =>
      line[1].clone().sub(line[0]);

    const vec1 = getLineVector(line1);
    const vec2 = getLineVector(line2);

    const radius = Math.min(vec1.length(), vec2.length()) / 2;
    const startVector = vec1.clone().normalize().multiplyScalar(radius);
    const end1 = line1[0].clone().add(startVector);

    const end2 = line2[0]
      .clone()
      .add(vec2.clone().normalize().multiplyScalar(radius));

    const angle = vec1.clone().angleTo(vec2);

    const points: Vector3[] = [];
    const axis = vec1.clone().cross(vec2).normalize();

    const division = 25;
    while (points.length < division - 1) {
      points.push(
        line1[0]
          .clone()
          .add(
            startVector
              .clone()
              .applyAxisAngle(axis, (angle / division) * (points.length + 1)),
          ),
      );
    }

    this.vertexs = [end1, ...points, end2];

    this.line = new CSLine({
      vertexs: this.vertexs,
      lineWidth: 1,
      color: AngleLabel.color,
    });

    this.add(this.line);
    this.drawArrows();

    this.measureLabel = new MeasureLabel({
      label: '角度',
      value: measureResult.value,
      type: ParameterObjectType.Angle,
      position: this.vertexs[Math.round(this.vertexs.length / 2)].toArray(),
      lineTo: this,
      labelColor: AngleLabel.color,
      labelPosition: measureResult.labelPosition,
    });

    this.add(this.measureLabel);
  }

  line: CSLine;
  measureLabel: MeasureLabel;
  arrows: CSArrow[];
  isArrowOut: boolean;

  drawArrows(force = false) {
    // 0.08 是测试结果,纯纯 magic number
    const isArrowOut =
      this.getRatioDistance(this.vertexs[0], this.vertexs.at(-1)) > 0.08;

    if (this.isArrowOut === isArrowOut && !force) {
      return;
    }

    this.isArrowOut = isArrowOut;

    const arrowAttrs = {
      color: this.getColor(),
      needKeepSize: true,
      radiusBottom: 0.2,
      height: 1,
      translate: [0, -0.4, 0] as Vector3Tuple,
      camera: ctx2.viewer.sceneSetup.oCamera,
    };

    this.arrows?.forEach((a) => a.destroy());

    const directions = [
      this.vertexs[0].clone().sub(this.vertexs[1]),
      this.vertexs.at(-1).clone().sub(this.vertexs.at(-2)),
    ];

    this.arrows = [
      new CSArrow({
        position: this.vertexs[0].toArray(),
        direction: (isArrowOut
          ? directions[0]
          : directions[0].negate()
        ).toArray(),
        ...arrowAttrs,
      }),
      new CSArrow({
        position: this.vertexs.at(-1).toArray(),
        direction: (isArrowOut
          ? directions[1]
          : directions[1].negate()
        ).toArray(),
        ...arrowAttrs,
      }),
    ];

    this.arrows.forEach((a) => a.addTo(this));

    ctx2.viewer.requestRender();
  }

  updateMatrixWorld(force?: boolean): void {
    super.updateMatrixWorld(force);

    this.drawArrows();
  }

  private getColor() {
    let color = AngleLabel.color;
    if (this.isHovered) {
      color = ColorSchema.hover.edge;
    } else if (this.isSelected) {
      color = ColorSchema.select.edge;
    }

    return color;
  }

  setHover(val: boolean) {
    super.setHover(val);
    this.measureLabel.setHover(val);
  }

  setSelect(val: boolean) {
    super.setSelect(val);
    this.measureLabel.setSelect(val);
  }

  renderHighlight() {
    this.drawArrows(true);
    this.line.setColor(this.getColor());

    ctx2.viewer.requestRender();
  }

  destroy() {
    this.parent.remove(this);
    this.children.forEach((c) =>
      (c as CSArrow | MeasureLabel | CSLine).destroy(),
    );
  }
}

export class MeasureAngle extends MeasureChildObject {
  private drawAngleLines(
    startPoint: Vector3,
    line1: Vector3[],
    line2: Vector3[],
    skipFirstLine = false,
  ) {
    const length = line1[0].clone().sub(line1[1]).length() / 2;

    const vector1 = line1[1]
      .clone()
      .sub(line1[0])
      .normalize()
      .multiplyScalar(length);
    const endPoint1 = startPoint.clone().add(vector1);
    if (!skipFirstLine) {
      const lineObj1 = new CSLine({
        vertexs: [startPoint, endPoint1],
        color: 0x000000,
      });
      this.lines.push(lineObj1);
      this.add(lineObj1);
    }

    const vector2 = line2[1]
      .clone()
      .sub(line2[0])
      .normalize()
      .multiplyScalar(length);
    const endPoint2 = startPoint.clone().add(vector2);
    const lineObj2 = new CSLine({
      vertexs: [startPoint, endPoint2],
      color: 0x000000,
    });
    this.lines.push(lineObj2);
    this.add(lineObj2);

    this.label = new AngleLabel(
      [startPoint, endPoint1],
      [startPoint, endPoint2],
      this.measureResult,
    );
    this.add(this.label);
  }

  label: AngleLabel;
  lines: CSLine[] = [];

  setHover(val: boolean) {
    super.setHover(val);
    this.label?.setHover(val);
  }

  setSelect(val: boolean) {
    super.setSelect(val);
    this.label?.setSelect(val);
  }

  renderHighlight() {
    let color = 0x000000;
    if (this.isHovered) {
      color = ColorSchema.hover.edge;
    } else if (this.isSelected) {
      color = ColorSchema.select.edge;
    }

    this.lines.forEach((line) => {
      line.setColor(color);
    });
  }

  constructor(
    public measureResult: MeasureAngleResultWithLabel,
    relatedDxids: string[],
  ) {
    super();

    const types = relatedDxids.map(this.getTypeOfMeasureTarget);

    const line1Vector3Array = measureResult.geometry.line1.map((v) =>
      new Vector3().fromArray(v),
    );
    const line2Vector3Array = measureResult.geometry.line2.map((v) =>
      new Vector3().fromArray(v),
    );

    // 线线夹角
    if (types.every((t) => t === PickType.LINE)) {
      const startPoint = line1Vector3Array[0]
        .clone()
        .add(line1Vector3Array[1])
        .multiplyScalar(0.5);

      this.drawAngleLines(
        startPoint,
        line1Vector3Array,
        line2Vector3Array,
        true,
      );
    }

    // 线面夹角/面面夹角
    const firstPlaneIndex = types.findIndex((t) => t === PickType.PLANE);

    if (firstPlaneIndex === -1) {
      return;
    }

    const planeDxid = relatedDxids[firstPlaneIndex];
    const targetPlaneLine =
      firstPlaneIndex === 0 ? line1Vector3Array : line2Vector3Array;
    const targetOtherLine =
      firstPlaneIndex === 0 ? line2Vector3Array : line1Vector3Array;

    const startPoint = getFaceCenterByPickId(planeDxid);

    if (startPoint == null) {
      return;
    }

    this.drawAngleLines(
      new Vector3().fromArray(startPoint),
      targetPlaneLine,
      targetOtherLine,
    );
  }

  private getTypeOfMeasureTarget(pickId: string) {
    if (/:f\d+$/.test(pickId)) {
      return PickType.PLANE;
    }

    if (/:e\d+$/.test(pickId)) {
      return PickType.LINE;
    }

    // note: when picked from design tree, the pickId is the dxid of the object
    const type = getRenderTypeByDxid(pickId);

    if ([PickType.PLANE, PickType.SURFACE].includes(type)) {
      return PickType.PLANE;
    }

    if ([PickType.LINE, PickType.CURVE].includes(type)) {
      return PickType.LINE;
    }

    return null;
  }

  destroy() {
    this.parent?.remove(this);

    this.children.forEach((c) => (c as AngleLabel | CSLine).destroy());
  }
}
